import * as vscode from 'vscode';
import { getBasePath } from '../utils/basePathUtils';
import { getProjectToken } from '../utils/tokenUtils';
import { ProjectCodeCommentApi } from '../proto-gen/api';
import { Configuration, CodeCommentInfo } from '../proto-gen';



export class CodeComment implements vscode.Comment {
    commentId: string;
    contextValue?: string;
    oldBody?: string | vscode.MarkdownString;
    constructor(commentId: string,
        public body: string | vscode.MarkdownString,
        public mode: vscode.CommentMode,
        public author: vscode.CommentAuthorInformation,
        public timestamp: Date,
        public thread: vscode.CommentThread,
    ) {
        this.commentId = commentId;
    }
}

const usageComment: vscode.Comment = {
    body: '所有评论都是以markdown的格式显示。评论内容通过凌鲨客户端获取。',
    mode: vscode.CommentMode.Preview,
    author: {
        name: "说明"
    },
    contextValue: "useage"
};


let commentController: vscode.CommentController | null = null;
let curDocUri = "";
let curDocThreadMap: Map<string, vscode.CommentThread> = new Map();

export function init(controller: vscode.CommentController) {
    commentController = controller;
}

export function onDocChange(document: vscode.TextDocument) {
    if (commentController == null) {
        return;
    }
    if (document.uri.scheme !== "file") {
        return;
    }
    if (document.uri.toString() != curDocUri) {
        //清除所有thread
        for (const thread of curDocThreadMap.values()) {
            thread.dispose();
        }
        curDocThreadMap = new Map();
        curDocUri = document.uri.toString();
    }
    //分析页面
    const newThreadRangeMap = parseDocument(document.getText());
    //清除不存在的thread
    let curKeys = Array.from(curDocThreadMap.keys());
    for (const curKey of curKeys) {
        if (!newThreadRangeMap.has(curKey)) {
            const thread = curDocThreadMap.get(curKey);
            if (thread != undefined) {
                thread.dispose();
            }
            curDocThreadMap.delete(curKey);
        }
    }
    //增加新的thread
    for (const newKeyValue of newThreadRangeMap.entries()) {
        if (!curDocThreadMap.has(newKeyValue[0])) {
            createThread(document.uri, newKeyValue[0], newKeyValue[1]);
        }
    }
}

async function createThread(docUri: vscode.Uri, threadUri: string, range: vscode.Range) {
    if (commentController == null) {
        return;
    }
    const thread = commentController.createCommentThread(docUri, range, [usageComment]);
    thread.label = threadUri;
    syncThread(thread);
    curDocThreadMap.set(threadUri, thread);
}

export async function syncThread(thread: vscode.CommentThread) {
    if (thread.label == undefined) {
        return;
    }
    const threadId = thread.label.replace("linksaas://comment/", "");
    const basePath = getBasePath();
    if (basePath == null) {
        return;
    }
    const api = new ProjectCodeCommentApi(new Configuration({
        basePath: basePath.basePath,
    }));
    const tokenInfo = getProjectToken();
    if (tokenInfo == null) {
        return;
    }
    const res = await api.projectProjectIdCodeCommentCommentThreadIdGet(tokenInfo.projectId, threadId, basePath.token);
    let commentList: vscode.Comment[] = [];
    res.data.forEach(item => {
        const comment = convertComment(item, thread);
        commentList.push(comment);
    });
    if (commentList.length == 0) {
        commentList = [usageComment];
    }
    thread.comments = commentList;
}

function parseDocument(content: string): Map<string, vscode.Range> {
    const retMap = new Map();
    const regex = /(linksaas:\/\/comment\/[a-zA-Z0-9]{21})/;
    content.split("\n").forEach((line: string, lineIndex: number) => {
        const match = line.match(regex);
        if (match != null && match.index != undefined) {
            const range = new vscode.Range(
                new vscode.Position(lineIndex, match.index),
                new vscode.Position(lineIndex, match.index + match[1].length)
            );
            retMap.set(match[1], range);
        }
    })
    return retMap;
}

export async function addComment(reply: vscode.CommentReply) {
    if (reply.thread.label == undefined) {
        return;
    }
    const threadId = reply.thread.label.replace("linksaas://comment/", "");
    const basePath = getBasePath();
    if (basePath == null) {
        return;
    }
    const api = new ProjectCodeCommentApi(new Configuration({
        basePath: basePath.basePath,
    }));
    const tokenInfo = getProjectToken();
    if (tokenInfo == null) {
        return;
    }
    const addRes = await api.projectProjectIdCodeCommentCommentThreadIdPut(tokenInfo?.projectId, threadId,
        basePath.token,
        {
            contentType: "markdown",
            content: reply.text,
        });
    const getRes = await api.projectProjectIdCodeCommentCommentThreadIdCommentIdGet(tokenInfo.projectId, threadId,
        addRes.data.commentId ?? "", basePath.token);
    reply.thread.comments = [...reply.thread.comments, convertComment(getRes.data, reply.thread)];
}

function convertComment(info: CodeCommentInfo, thread: vscode.CommentThread): CodeComment {
    const comment = new CodeComment(info.commentId ?? "", new vscode.MarkdownString(info.content ?? ""),
        vscode.CommentMode.Preview,
        {
            name: info.userDisplayName ?? "",
        }, new Date(info.updateTime ?? 0), thread);
    const canUpdate = info.canUpdate ?? false;
    const canRemove = info.canRemove ?? false;
    if (canUpdate && canRemove) {
        comment.contextValue = "update|remove";
    } else if (canUpdate) {
        comment.contextValue = "update";
    } else if (canRemove) {
        comment.contextValue = "remove";
    }
    return comment;
}

export function setEditComment(comment: CodeComment) {
    const comments = comment.thread.comments.map(cmt => {
        if ((cmt as CodeComment).commentId == comment.commentId) {
            (cmt as CodeComment).oldBody = cmt.body;
            cmt.mode = vscode.CommentMode.Editing;
        }
        return cmt;
    })
    comment.thread.comments = comments;
}

export function cancelSaveComment(comment: CodeComment) {
    const comments = comment.thread.comments.map(cmt => {
        if ((cmt as CodeComment).commentId == comment.commentId) {
            const oldBody = (cmt as CodeComment).oldBody;
            if (oldBody != undefined) {
                cmt.body = oldBody;
            }
            cmt.mode = vscode.CommentMode.Preview;
        }
        return cmt;
    });
    comment.thread.comments = comments;
}

export async function saveComment(comment: CodeComment) {
    if (comment.thread.label == undefined) {
        return;
    }
    const threadId = comment.thread.label.replace("linksaas://comment/", "");
    const basePath = getBasePath();
    if (basePath == null) {
        return;
    }
    const api = new ProjectCodeCommentApi(new Configuration({
        basePath: basePath.basePath,
    }));
    const tokenInfo = getProjectToken();
    if (tokenInfo == null) {
        return;
    }
    await api.projectProjectIdCodeCommentCommentThreadIdCommentIdPost(tokenInfo.projectId, threadId, comment.commentId,
        basePath.token,
        {
            contentType: "markdown",
            content: typeof (comment.body) == "string" ? comment.body : comment.body.value,
        });
    const comments = comment.thread.comments.map(cmt => {
        if ((cmt as CodeComment).commentId == comment.commentId) {
            (cmt as CodeComment).oldBody = undefined;
            (cmt as CodeComment).timestamp = new Date();
            cmt.mode = vscode.CommentMode.Preview;
        }
        return cmt;
    });
    comment.thread.comments = comments;
}

export async function removeComment(comment: CodeComment) {
    if (comment.thread.label == undefined) {
        return;
    }
    const threadId = comment.thread.label.replace("linksaas://comment/", "");
    const basePath = getBasePath();
    if (basePath == null) {
        return;
    }
    const api = new ProjectCodeCommentApi(new Configuration({
        basePath: basePath.basePath,
    }));
    const tokenInfo = getProjectToken();
    if (tokenInfo == null) {
        return;
    }
    await api.projectProjectIdCodeCommentCommentThreadIdCommentIdDelete(tokenInfo.projectId, threadId, comment.commentId, basePath.token);
    const comments = comment.thread.comments.filter(cmt => (cmt as CodeComment).commentId != comment.commentId);
    comment.thread.comments = comments;
}